#pragma once

#include <ostream>
#include <istream>
#include <cstdlib>

template <class T> 
class Vector {
public:

	Vector (const size_t n){
		dimension = n;
		data = new T[n];

		for( size_t i = 0; i < n; i++) {
			data[i] = T();
		}
	}

	Vector (const size_t n, const T* another){
		if( NULL == another ) {
			throw std::invalid_argument ("Pointer to another data cannot be NULL");
		}
		dimension = n;
		data = new T[n];

		for ( size_t i = 0; i < n; i++) {
			data[i] = another[i];
		}
	}

	Vector (const Vector <T>& another){
		dimension = another.dimension;
		data = new T[dimension];

		for ( size_t i = 0; i < dimension; i++){
			data[i] = another.data[i];
		}
	}

	Vector (const size_t n, const Vector <T>& another){
		dimension = n;
		data = new T[n];
		size_t dataBarrier = ( dimension > another.dimension ) ? another.dimension : dimension;

		for ( size_t i = 0; i < dataBarrier; i++ ) {
			data[i] = another.data[i];
		}

		for ( size_t i = dataBarrier; i < dimension; i++ ) {
			data[i] = T();
		}
	}

	~Vector() {
		delete[] data;
	}




	size_t size() const {
		return dimension;
	}





	T& operator[](const size_t index) const {
		if( !isIndexValid(index) ){
			throw std::out_of_range("");
		}
		return data[index];
	}


	T& operator[](const size_t index){
		if( !isIndexValid(index) ){
			throw std::out_of_range("");
		}
		return data[index];
	}


	Vector <T>& operator= (const Vector <T>& another)
	{

		if( this == &another )
		{
			return *this;
		}

		if (dimension != another.dimension)
		{
			delete[] data;
			dimension = another.dimension;
			data = new T[dimension];
		}
		for ( size_t i = 0; i < dimension; i++ )
		{
			data[i] = another.data[i];
		}

		return *this;
	}


	Vector <T>& operator+= (const Vector <T>& another)
	{
		if (dimension < another.dimension)
		{	
			T *newData = new T[another.dimension];
			for(size_t i = 0; i < dimension; i++) {
				newData[i] = data[i];
			}
			for( size_t i = dimension; i < another.dimension; i++) {
				newData[i] = T();
			}
			delete[] data;
			data = newData;
			dimension = another.dimension;
		}

		for ( size_t i = 0; i < dimension; i++ )
		{
			data[i] += another.data[i];
		}
		return *this;
	}


	Vector <T>& operator*= (const T& constant)
	{
		for (size_t i = 0; i < this->dimension; i++)
		{
			data[i] *= constant;
		}

		return *this;
	}


	Vector<T>& operator-= (Vector <T>& another)
	{
		if (dimension < another.dimension)
		{	
			T *newData = new T[another.dimension];
			for(size_t i = 0; i < dimension; i++) {
				newData[i] = data[i];
			}
			for( size_t i = dimension; i < another.dimension; i++) {
				newData[i] = T();
			}
			delete[] data;
			data = newData;
			dimension = another.dimension;
		}

		for ( size_t i = 0; i < dimension; i++ )
		{
			data[i] -= another.data[i];
		}
		return *this;
	}

	void exchange(size_t firstIndex, size_t secondIndex) {
		if( !isIndexValid(firstIndex) || !isIndexValid(secondIndex) ) {
			throw std::invalid_argument("Exchange indexes are invalid");
		}
		T tmp = data[firstIndex];
		data[firstIndex] = data[secondIndex];
		data[secondIndex] = tmp;
	}

	template<class T>
	friend
		std::ostream& operator << (std::ostream& outputStream, const Vector<T>& vector);

	template<class T>
	friend
		std::istream& operator >> (std::istream& inputStream, Vector<T>& vector);

	template<class T>
	friend
		Vector<T>& operator* (const T& constant, Vector<T>& another);

	template<class T>
	friend
		Vector<T>& operator* (Vector<T>& another, const T& constant);

	template<class T>
	friend
		Vector<T> operator+( Vector<T>& first, Vector<T>& second);

	template<class T>
	friend
		T operator*(const Vector<T>& first, const Vector<T>& second);

	template<class T>
	friend
		Vector<T> operator-(Vector<T>& first, Vector<T>& second);

private:
	bool isIndexValid ( const size_t index ) const {
		return ( index >= 0 && index < dimension );
	}

private:
	size_t dimension;
	T *data;

};

template<class T>
std::ostream& operator << (std::ostream& outputStream, const Vector<T>& vector) {
	outputStream << "{";

	for( size_t i = 0; i < vector.dimension - 1; i++ ) {
		outputStream << vector.data[i] << ", ";
	}
	if (vector.dimension > 0)
	{
		outputStream << vector.data[vector.dimension - 1];
	}
	outputStream << "}";
	return outputStream;
}

template<class T>
std::istream& operator >> (std::istream& inputStream, Vector<T>& vector) {
	if( 0 == vector.dimension ) {
		return inputStream;
	}

	Vector<T> vectorToRead (vector.dimension);

	char  vectorSeparator;
	inputStream >> vectorSeparator;
	if( inputStream.fail() || vectorSeparator != '{' ) {
		throw std::invalid_argument ("Vector begin is not {");
	}

	char delimiter;
	for(size_t i = 0; i < vectorToRead.dimension - 1; i++ ) {
		inputStream >> vectorToRead.data[i];
		inputStream >> delimiter;
		if( inputStream.fail() || delimiter != ',' ) {
			throw std::invalid_argument ("Vector components must have a delimiter ,");
		}
	}
	inputStream >> vectorToRead.data[vectorToRead.dimension - 1];
	inputStream >> vectorSeparator;
	if( inputStream.fail() || vectorSeparator != '}' ) {
		throw std::invalid_argument ("Vector end is not }");
	}

	vector = vectorToRead;
	return inputStream;
}

template<class T>
Vector<T>& operator* (const T& constant, Vector<T>& another) {
	for (size_t i = 0; i < another.dimension; i++)
	{
		another.data[i] *= constant;
	}
	return another;
}

template<class T>
Vector<T>& operator* (Vector<T>& another, const T& constant) {
	return operator* (constant, another);
}

template<class T>
Vector<T> operator+( Vector<T>& first, Vector<T>& second) {
	size_t maxDimension = Math::max (first.dimension, second.dimension);
	Vector result (maxDimension,first);

	for (size_t i = 0; i < second.dimension; i++)
	{
		result.data[i] += second.data[i];
	}
	return result;
}

template<class T>
T operator*(const Vector<T>& first, const Vector<T>& second) {
	size_t mindimenion = Math::min<size_t>(first.dimension, second.dimension);
	T result = 0.0;
	for ( size_t i = 0; i < mindimenion; i++ )
	{
		result += (first.data[i] * second.data[i]);
	}

	return result;
}

template<class T>
Vector<T> operator-(Vector<T>& first, Vector<T>& second) {
	size_t maxDimension = Math::max (first.dimension, second.dimension);
	Vector result (maxDimension,first);

	for (size_t i = 0; i < second.dimension; i++)
	{
		result.data[i] -= second.data[i];
	}
	return result;
}

